%{
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "util.h"
#include "y.tab.h"

  struct table * t;
  int r = 0;
  int br=0;

  %}

%token AND OR CLASS THEN ELSE END WHILE DO DEF LEQ GEQ
%token FOR TO RETURN IN NEQ
%left '*' 
%left '/'
%left '+' '-'
%left '<' '>' LEQ GEQ EQ
%left AND OR

%token <i> INT
%token <f> FLOAT
%token <s> STRING ID
%token <v> IF
%type <v> lhs primary expr comp_expr additive_expr multiplicative_expr 
%type <str> stmt stmts topstmt topstmts program opt_else
%type <term> term terms opt_terms 
%type <e> exprs

%union {
  int i;
  float f;
  char * s;
  char term;
  struct var * v;
  struct sdc * str;
  struct exprs * e;
}

%%
program		:  topstmts opt_terms {}
;
topstmts        : {}
| topstmt {}
| topstmts terms topstmt {}
;
topstmt	        : CLASS ID term stmts END {}
| CLASS ID '<' ID term stmts END {}
| stmt
{
  fprintf(OUT,$1->code);
}
;

stmts	        : /* none */ 
{
  $$ = init_sdc();
}

| stmt {$$=$1;}

| stmts terms stmt
{
  struct sdc * v = init_sdc();
  concatener(&(v->code),$1->code);
  concatener(&(v->code),$3->code);
  ajouter_sdc(t,v);
  $$ = v;
}
;

stmt		:

IF 
{struct var * v = creer_sentinelle(); ajouter_loc(t,v);$1=v;}

expr THEN stmts terms
{
  char tmp[MAX_LINE_SIZE];
  if($3->type != TYPE_BOOLEAN){
    yyerror("L'expression n'est pas un booléen"); // A faire : cast de int ou float vers booléen
    exit(EXIT_FAILURE);
  }
  else{
    if($3->calculable == CALCULABLE) {
      if($3->u.i){
	concatener(&($1->code),$5->code);
      }
    }
    else {
      // if expr then
      concatener(&($1->code),$3->code);
      sprintf(tmp,"br i1 %%r%d, label %%br%d, label %%br%d\nbr%d:\n",$3->r,br,br+1,br);
      concatener(&($1->code),tmp);
      concatener(&($1->code),$5->code);
      br += 2;
    }
    depiler_bloc(t);
  }
}

opt_else
{
  if($3->calculable == CALCULABLE) {
    if(!($3->u.i)){
      concatener(&($1->code),$8->code);
    }
  }
  else{
    char tmp[MAX_LINE_SIZE];
    
    // else
    if ($8->type_else == TYPE_ELSE){
      sprintf(tmp,"br label %%br%d\nbr%d:\n",br,br-1);
      concatener(&($1->code),tmp);
      concatener(&($1->code),$8->code);
      sprintf(tmp,"br label %%br%d\nbr%d:\n",br,br);
      
      depiler_bloc(t);
    }
    else{
      sprintf(tmp,"br label %%br%d\nbr%d:\n",br-1,br-1);
    }
    concatener(&($1->code),tmp);
    
    br += 1;
  }
}

END
{
  struct sdc * v = init_sdc();
  concatener(&(v->code),$1->code);
  ajouter_sdc(t,v);
  $$ = v;
}

| FOR
{struct var * v = creer_sentinelle(); ajouter_loc(t,v);}

ID
{
  struct var * id = init_id(t,$3,&r);
  char tmp[MAX_LINE_SIZE];
  if(id->type == TYPE_UNDEF){
    sprintf(tmp,"%%r%d = alloca i32\n",id->r);
    concatener(&(id->code),tmp);
    id->type = TYPE_INT;
  }
  else{
    if(id->type != TYPE_INT){
      yyerror("Le compteur est déjà défini est n'est pas entier");
      exit(EXIT_FAILURE);
    }
  }
}

IN expr TO expr DO term stmts terms END
{
  struct sdc * v = init_sdc();
  char tmp[MAX_LINE_SIZE];
  if(($6->type != TYPE_INT) || ($8->type != TYPE_INT)){
    yyerror("Les bornes ne sont pas des entiers"); // Conversion ?
    exit(EXIT_FAILURE);
  }
  else{

    // Calcul de la première expression
    concatener(&(v->code),$6->code);

    // Calcul de la deuxième expression
    concatener(&(v->code),$8->code);

    struct var * id = init_id(t,$3,&r);

    if(id->type != TYPE_INT){
      yyerror("Le compteur est déjà défini est n'est pas entier");
      exit(EXIT_FAILURE);
    }
    int i=0;
    while(id->code[i] != '\n'){
      i++;
    }
    snprintf(tmp,i+2,id->code);
    concatener(&(v->code),tmp);

    //Initialisation du compteur
    if($6->calculable == CALCULABLE){
      sprintf(tmp,"store i32 %d, i32* %%r%d\n",$6->u.i, id->r);
    }
    else{
      sprintf(tmp,"store i32 %%r%d, i32* %%r%d\n",$6->r,id->r);
    }
    concatener(&(v->code),tmp);

    sprintf(tmp,"br label %%br%d\n",br); // Jump vers le label du for
    concatener(&(v->code),tmp);

    sprintf(tmp,"br%d:\n",br);
    br++;
    concatener(&(v->code),tmp);

    sprintf(tmp,"%%r%d = load i32* %%r%d\n",r,id->r); // Load du compteur
    concatener(&(v->code),tmp);
    sprintf(tmp,"%%r%d = add i32 %%r%d, 1\n",r+1,r); // Incrémentation
    concatener(&(v->code),tmp);
    sprintf(tmp,"store i32 %%r%d, i32* %%r%d\n",r+1,id->r); // Store du compteur
    concatener(&(v->code),tmp);
    
    if($8->calculable == CALCULABLE){
      sprintf(tmp,"%%r%d = icmp sle i32 %%r%d, %d\n",r+2,r+1,$8->u.i); // Comparaison
    }
    else{
      sprintf(tmp,"%%r%d = icmp sle i32 %%r%d, %%r%d\n",r+2,r+1,$8->r); // Comparaison
    }
    concatener(&(v->code),tmp);
    
    sprintf(tmp,"br i1 %%r%d, label %%br%d, label %%br%d\nbr%d:\n",r+2,br,br+1,br); // Branchement conditionnel
    concatener(&(v->code),tmp);
    
    concatener(&(v->code),$11->code); // Code de la boucle
    
    sprintf(tmp,"br label %%br%d\nbr%d:\n",br-1,br+1);
    concatener(&(v->code),tmp);
    
    r += 3;
    br += 2;
  }
  $$=v;
  ajouter_sdc(t,v);
  depiler_bloc(t);
}

| WHILE
{struct var * v = creer_sentinelle(); ajouter_loc(t,v);}

expr DO term stmts terms END
{
  struct sdc * v = init_sdc();
  char tmp[MAX_LINE_SIZE];
  if($3->type != TYPE_BOOLEAN){
    yyerror("L'expression n'est pas un booléen"); // A faire : cast de int ou float vers booléen
    exit(EXIT_FAILURE);
  }
  else{

    sprintf(tmp,"br label %%br%d\n",br);
    concatener(&(v->code),tmp);
    
    sprintf(tmp,"br%d:\n",br);
    br++;
    concatener(&(v->code),tmp);
      
    if($3->calculable == CALCULABLE){
      sprintf(tmp,"%%r%d = add %d, 0\n",$3->r,$3->u.i);
      concatener(&(v->code),tmp);
    }
    else{
      concatener(&(v->code),$3->code);
    }
    
    sprintf(tmp,"br i1 %%r%d, label %%br%d, label %%br%d\nbr%d:\n",$3->r,br,br+1,br);
    concatener(&(v->code),tmp);
    concatener(&(v->code),$6->code);
    
    sprintf(tmp,"br label %%br%d\nbr%d:\n",br-1,br+1);
    concatener(&(v->code),tmp);
    
    br += 2;
  }
  $$=v;
  ajouter_sdc(t,v);
  depiler_bloc(t);
}

| lhs '=' expr
{
  struct sdc * v = affectation($1,$3);
  $$ = v;
}

| lhs
{
  struct sdc * v = init_sdc();
  concatener(&(v->code),$1->code);
  $$ = v;
}

| RETURN expr 
{
  struct sdc * v=init_sdc();
  char tmp[MAX_LINE_SIZE];
  if($2->calculable==CALCULABLE) {
    switch($2->type){
    case TYPE_INT:
      sprintf(tmp,"ret i32 %d",$2->u.i);
      break;
    case TYPE_FLOAT:
      sprintf(tmp,"ret float %e",$2->u.f);
      break;
    case TYPE_STRING:
      //TO DO
      break;
    default:
      yyerror("Type non reconnu\n");
      break;
    }
  }
  else {
    switch($2->type){
    case TYPE_INT:
      sprintf(tmp,"ret i32 %%r%d",$2->r);
      break;
    case TYPE_FLOAT:
      sprintf(tmp,"ret float %%r%d",$2->r);
      break;
    case TYPE_STRING:
      //TO DO
      break;
    default:
      yyerror("Type non reconnu\n");
      break;
    }
  }
  concatener(&(v->code),tmp);
  $$ = v;
}

| DEF ID opt_params term stmts terms END {}
;


opt_else        : /* none */ 
{
  $$ = init_sdc();
}

| ELSE
{struct var * v = creer_sentinelle(); ajouter_loc(t,v);}

stmts terms
{
  struct sdc * v = init_sdc();
  concatener(&(v->code),$3->code);
  ajouter_sdc(t,v);
  v->type_else = TYPE_ELSE;
  $$ = v;
}
;

opt_params      : /* none */
| '(' ')'
| '(' params ')'
;
params          : ID ',' params
| ID
; 
lhs             : ID
{
  $$ = init_id(t,$1,&r);
}

| ID '.' primary
{
}

| ID '(' ')'
{
}

| ID '(' exprs ')'
{
  $$ = function_print($1,$3,&r);
}
;
exprs           : exprs ',' expr
{
  ajouter_expr($1,$3);
  $$ = $1;
}

| expr
{
  struct exprs * e = init_exprs();
  ajouter_expr(e,$1);
  $$ = e;
}

;
primary         : lhs
{
  if($1->type == TYPE_UNDEF) {
    yyerror("identifiant non défini");
  }
  $1->calculable = NON_CALCULABLE; // Si lhs est dans une expression, il n'est plus forcément calculable
  $$ = $1;
}

| STRING
{
  struct var * v = creer_var_string(NULL,$1,r);
  ajouter_loc(t,v);
  $$ = v;
  r++;
}

| FLOAT 
{
  struct var * v = creer_var_float(NULL,$1,r,CALCULABLE);
  ajouter_loc(t,v);
  $$ = v;
  r++;
}

| INT
{
  struct var * v = creer_var_int(NULL,$1,r,CALCULABLE);
  ajouter_loc(t,v);
  $$ = v;
  r++;
}

| '(' expr ')' 
{
  $$=$2;
}
;
expr            : expr AND comp_expr
{
  if($1->type == TYPE_FLOAT || $3->type == TYPE_FLOAT){
    yyerror("Opération 'and' sur des flottants\n");
    exit(EXIT_FAILURE);
  }
  struct var * v = expr($1,$3,"and","and",bool_comp_int($1->u.i,$3->u.i,"a"),0,bool_comp_float($1->u.f,$3->u.f,"a"),&r,1);
  ajouter_tmp(t,v);
  if($1->type == TYPE_BOOLEAN || $3->type == TYPE_BOOLEAN){
    v->type = TYPE_BOOLEAN;
  }
  $$ = v;
}

| expr OR comp_expr
{
  if($1->type == TYPE_FLOAT || $3->type == TYPE_FLOAT){
    yyerror("Opération 'and' sur des flottants\n");
    exit(EXIT_FAILURE);
  }
  struct var * v = expr($1,$3,"or","or",bool_comp_int($1->u.i,$3->u.i,"o"),0,bool_comp_float($1->u.f,$3->u.f,"o"),&r,1);
  ajouter_tmp(t,v);
  if($1->type == TYPE_BOOLEAN || $3->type == TYPE_BOOLEAN){
    v->type = TYPE_BOOLEAN;
  }
  $$ = v;
}

| comp_expr
{
  $$=$1;
}

;
comp_expr       : additive_expr '<' additive_expr
{
  struct var * v = expr($1,$3,"icmp slt","fcmp olt",bool_comp_int($1->u.i,$3->u.i,"<"),0,bool_comp_float($1->u.f,$3->u.f,"<"),&r,1);
  v->type = TYPE_BOOLEAN;
  ajouter_tmp(t,v);
  $$ = v;
}


| additive_expr '>' additive_expr
{
  struct var * v = expr($1,$3,"icmp sgt","fcmp ogt",bool_comp_int($1->u.i,$3->u.i,">"),0,bool_comp_float($1->u.f,$3->u.f,">"),&r,1);
  v->type = TYPE_BOOLEAN;
  ajouter_tmp(t,v);
  $$ = v;
}

| additive_expr LEQ additive_expr
{
  struct var * v = expr($1,$3,"icmp sle","fcmp ole",bool_comp_int($1->u.i,$3->u.i,"<="),0,bool_comp_float($1->u.f,$3->u.f,"<="),&r,1);
  v->type = TYPE_BOOLEAN;
  ajouter_tmp(t,v);
  $$ = v;
}

| additive_expr GEQ additive_expr
{
  struct var * v = expr($1,$3,"icmp sge","fcmp oge",bool_comp_int($1->u.i,$3->u.i,">="),0,bool_comp_float($1->u.f,$3->u.f,">="),&r,1);
  v->type = TYPE_BOOLEAN;
  ajouter_tmp(t,v);
  $$ = v;
}

| additive_expr EQ additive_expr
{
  struct var * v = expr($1,$3,"icmp eq","fcmp oeq",bool_comp_int($1->u.i,$3->u.i,"="),0,bool_comp_float($1->u.f,$3->u.f,"="),&r,1);
  v->type = TYPE_BOOLEAN;
  ajouter_tmp(t,v);
  $$ = v;
}

| additive_expr NEQ additive_expr
{
  struct var * v = expr($1,$3,"icmp ne","fcmp one",bool_comp_int($1->u.i,$3->u.i,"!="),0,bool_comp_float($1->u.f,$3->u.f,"!="),&r,1);
  v->type = TYPE_BOOLEAN;
  ajouter_tmp(t,v);
  $$ = v;
}

| additive_expr
{
  $$ = $1;
}
;

additive_expr   : multiplicative_expr
{
  $$=$1;
}

| additive_expr '+' multiplicative_expr
{
  struct var * v = expr($1,$3,"add","fadd",$1->u.i+$3->u.i,$1->u.f+$3->u.f,0,&r,0);
  ajouter_tmp(t,v);
  $$ = v;
}

| additive_expr '-' multiplicative_expr
{
  struct var * v = expr($1,$3,"sub","fsub",$1->u.i-$3->u.i,$1->u.f-$3->u.f,0,&r,0);
  ajouter_tmp(t,v);
  $$ = v;
}
;
multiplicative_expr : multiplicative_expr '*' primary
{
  struct var * v = expr($1,$3,"mul","fmul",$1->u.i*$3->u.i,$1->u.f*$3->u.f,0,&r,0);
  ajouter_tmp(t,v);
  $$ = v;
}

| multiplicative_expr '/' primary 
{
  struct var * v = expr($1,$3,"sdiv","fdiv",$1->u.i/$3->u.i,$1->u.f/$3->u.f,0,&r,0);
  ajouter_tmp(t,v);
  $$ = v;
}

| primary
{
  $$=$1;
}
;

opt_terms	: /* none */ {$$='\0';}
| terms {$$=$1;}
;

terms		: terms ';' {$$=';';}
| terms '\n' {$$='\n';}
| ';' {$$=';';}
| '\n'{$$='\n';}
;
term            : ';'  {$$=';';}
| '\n' {$$='\n';}
;
%%

void print_begin(){
  fprintf(OUT,"define i32 @main() {\n");
}

void print_func(){
  fprintf(OUT,"declare void @print_i(i32)\ndeclare void @print_f(float)\ndeclare void @print_s(i8*)\n");
}

void print_end(){
  fprintf(OUT,"ret i32 0\n}");
}

int main() {
  t = init_table();
  
  print_func();
  print_begin();
  yyparse();
  print_end();

  supprimer_table(t);
  return 0;
}
